/**
 * 
 */
package ac.notification.manager;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;

import ac.notification.event.INotificationListener;
import ac.notification.event.NotificationEvent;

/**
 * @author Ankush Chaudhary
 *
 */
public class DefaultNotificationManager implements INotificationManager {

	

	//Data structures to hold dynamic information.
	private static ConcurrentLinkedQueue<NotificationEvent> notificationQueue 				= new ConcurrentLinkedQueue<NotificationEvent>();
	private static Map<String,Set<INotificationListener>> notificationSubscriptionMap		= new HashMap<String, Set<INotificationListener>>();

	//Thread pool to dispatch event.
	private Thread dispatcherThread;

	// Flag that represent various states.
	private boolean updatingMemberships = false;
	private boolean logging = false;
	
	private boolean dispatchInterrupted = false;

	public DefaultNotificationManager()
	{
		startDispatcherThread();
	}


	private void startDispatcherThread() {
		dispatcherThread = new Thread(new Runnable() {
			@Override
			public void run() {
				for(;!(dispatchInterrupted);)
				{
					synchronized (notificationQueue) {
						while(notificationQueue.isEmpty())
						{
							try {
								notificationQueue.wait();
							} catch (InterruptedException e) {
								e.printStackTrace();
							}
						}
						NotificationEvent notificationEvent = notificationQueue.remove();
						Set<INotificationListener> subscribers = notificationSubscriptionMap.get(notificationEvent.getPropertyIdentifier());
						if(null != subscribers)
						{
							for(INotificationListener subscriber : subscribers)
							{
								print("Dispatching Event:" + notificationEvent + " to [" + subscriber.getClass().getSimpleName() + "]");
								try{
								subscriber.receiveEvent(notificationEvent);
								}catch(Exception e)
								{
									error("Error occurred while dispatching event to  [" +  subscriber.getClass().getSimpleName() + "] Event: " + notificationEvent.getNotificationId());
								}
							}
						}
						else
						{
							error("No listener found for identifier:" + notificationEvent.getPropertyIdentifier());
						}

					}

				}
			}
		},"Notification Event Dispatcher!");
		dispatcherThread.setDaemon(true);
		dispatcherThread.start();

	}


	/* (non-Javadoc)
	 * @see ac.notification.manager.INotificationManager#dispatchNotification(ac.notification.event.NotificationEvent)
	 */
	@Override
	public void dispatchNotification(NotificationEvent notificationEvent) {
		synchronized (notificationQueue) {
			notificationQueue.add(notificationEvent);
			notificationQueue.notify();
		}


	}

	/* (non-Javadoc)
	 * @see ac.notification.manager.INotificationManager#registerListener(ac.notification.event.INotificationListener)
	 */
	@Override
	public void registerListener(INotificationListener notificationListener) {
		updatingMemberships = true;

		//Get list of identifiers a Listener is interested in.
		Set<String> identifiersSet = notificationListener.declaredSubscriptionSet();
		if(null == identifiersSet || identifiersSet.isEmpty())
		{
			return;
		}

		System.out.println("[" + this.getClass().getSimpleName() + "]Listener: " + notificationListener.getClass().getSimpleName() + "\t Subscription: " + identifiersSet);

		for(String notificationIdentifier : identifiersSet)
		{
			if(notificationSubscriptionMap.keySet().contains(notificationIdentifier))
			{
				Set<INotificationListener> listenersList = notificationSubscriptionMap.get(notificationIdentifier);
				listenersList.add(notificationListener);                    
			}
			else
			{
				Set<INotificationListener> listenersList = new HashSet<INotificationListener>();
				listenersList.add(notificationListener);
				notificationSubscriptionMap.put(notificationIdentifier, listenersList);
			}
		}

		updatingMemberships = false;
	}

	/* (non-Javadoc)
	 * @see ac.notification.manager.INotificationManager#removeListener(ac.notification.event.INotificationListener)
	 */
	@Override
	public void removeListener(INotificationListener notificationListener) {
		updatingMemberships = true;
		Set<String> identifiersSet = notificationListener.declaredSubscriptionSet();
		for(String identifier :  identifiersSet)
		{
			Set<INotificationListener> subscribers = notificationSubscriptionMap.get(identifier);
			if(null != subscribers)
			{
				subscribers.remove(notificationListener);
			}			
		}
		updatingMemberships = false;
	}

	/* (non-Javadoc)
	 * @see ac.notification.manager.INotificationManager#updateSubscriptions(ac.notification.event.INotificationListener)
	 */
	@Override
	public void updateSubscriptions(INotificationListener notificationListener) {
		updatingMemberships = true;
		removeListener(notificationListener);
		registerListener(notificationListener);
		updatingMemberships = false;
	}


	@Override
	public void setLogging(boolean logging) {
		this.logging = logging;

	}

	private void print(Object obj)
	{
		if(logging)
		{
			System.out.println("[" +this.getClass().getSimpleName()+ "][*]" + obj.toString());
		}
	}

	private void error(Object obj)
	{
		if(logging)
		{
			System.err.println("[" +this.getClass().getSimpleName()+ "][ERR]" + obj.toString());
		}
	}


	@Override
	public void haultEventDispatch() {
		notificationQueue.clear();
		synchronized (notificationQueue) {
			notificationQueue.notifyAll();
		}
		dispatchInterrupted = true;		
	}
}

